package com.hup.sokoban.view.customView;

import com.hup.sokoban.constant.MapConstant;
import com.hup.sokoban.constant.MapConstant.ElementType;
import com.hup.sokoban.model.common.MapItem;
import com.hup.utils.algorithm.image.GaussianBlur;
import com.hup.utils.commons.exception.AppExceptionHandler;
import com.hup.utils.commons.exception.ErrInfo;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.commons.start.SpringContext;
import com.hup.utils.swing.GraphicsUtil;
import com.hup.utils.swing.IconUtil;
import com.hup.utils.swing.ImageCacher;
import com.hup.utils.swing.ImageCacher.ImageCacherObserver;
import com.hup.utils.swing.ImageCacher.ImageInfo;
import com.hup.utils.swing.ImageUtil;
import com.hup.utils.swing.model.DefaultListModel2;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Objects;

/**
 * 绘制地图的label
 *
 * @author hugan
 * @date 2020/4/29
 */
@Log4j2
public class MapLabel extends JLabel {
    /**
     * 设计关卡时,辅助信息字体色
     */
    private static final Font COOR_FONT = new Font("宋体", Font.PLAIN, (int) (14 * LookThemeUtil.getScreenScale()));
    /**
     * [地图不完整]信息的字体
     */
    private static final Font NOT_SAFE_MSG_FONT = LookThemeUtil.getDefaultScaledFont();
    /**
     * 阿狸区域的半透明背景色
     */
    private static final Color EMPTY_COLOR = new Color(255, 255, 255, 120);
    private static final String NEVER_INIT = "NEVER_INIT";
    private static final Image ICON_READONLY = IconUtil.getIcon("/img/main/icon_readonly.gif", "只读").getImage();

    /**
     * true-设计器模式(会绘制文字信息等);false-一般模式
     */
    @Setter
    @Getter
    private boolean isDesigner;

    private ImageCacher imageCacher;
    /**
     * nullable:所在的JList组件,用于 {@link ImageCacherObserver}
     */
    private JList parentList;
    private int curIndexInList;

    @Getter
    private MapItem mapItem;

    /**
     * 当前背景图的路径,null-内置背景,notNull-用户定的背景
     */
    private String lastBackgroundPath = NEVER_INIT;
    /**
     * 当前背景图
     */
    private BufferedImage backgroundImage, backgroundImageBlur;
    /**
     * 外置背景加载错误信息
     */
    private String outerBackgroundErr;

    /**
     * 背景图尺寸
     */
    private int bgWidth, bgHeight;

    /**
     * 单元格边长,padding
     */
    private int cellSize, paddingLeft, paddingTop;
    /**
     * 只读状态:true-屏蔽游戏按键,展示只读图标
     */
    @Setter
    private boolean readOnly = false;


    public MapLabel() {
        setFocusable(true);
        setFont(COOR_FONT);
        imageCacher = SpringContext.getBean(ImageCacher.class);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                requestFocus();
            }
        });
    }

    @Override
    public void setBounds(int x, int y, int width, int height) {
        super.setBounds(x, y, width, height);
        log.debug("width={}, height={}, mapItem={}", width, height, mapItem);
        updateCellBounds();
    }

    /**
     * 根据组件和地图的尺寸比例,计算单元格:尺寸,padding
     */
    private void updateCellBounds() {
        int pWid = getWidth();
        int pHei = getHeight();
        if (pWid * pHei == 0 || mapItem == null) return;

        int col = mapItem.getWidth();
        int row = mapItem.getHeight();
        double rParent = pWid / (pHei + 0d);
        double rMap = col / (row + 0d);

        //格子边长,单位int-Graphics只能drawInt
        if (rParent <= rMap) {
            //上下空白
            cellSize = pWid / col;
        } else {
            //左右空白
            cellSize = pHei / row;
        }

        //不能整除的余数偏移量
        paddingLeft = (pWid - cellSize * col) / 2;
        paddingTop = (pHei - cellSize * row) / 2;
        log.debug("rParent={}, rMap={}, padding[Left,Top]=[{}, {}]", rParent, rMap, paddingLeft, paddingTop);
    }

    @Override
    public void paint(Graphics g2) {
        super.paint(g2);
        if (mapItem == null) return;
        Graphics2D g = GraphicsUtil.antiAlias((Graphics2D) g2);

        drawBackground(g);
        int pWid = getWidth();
        int pHei = getHeight();

        drawCells(g);

        //外置背景加载错误信息
        if (outerBackgroundErr != null) {
            g.setColor(Color.CYAN);
            g.drawString(outerBackgroundErr, 0, pHei - 3);
        }

        //[地图不完整]信息
        String notSafeMsg = mapItem.getNotSafeMsg();
        if (notSafeMsg != null) {
            FontMetrics metrics = getFontMetrics(NOT_SAFE_MSG_FONT);
            int padding = 10;
            int fontHeight = metrics.getHeight();
            int fontWidth = metrics.stringWidth(notSafeMsg) + padding * 2;
            int x = (pWid - fontWidth) / 2;
            if (x < 0) x = 0;//处理[关卡列表]文字太长

            g.setColor(Color.LIGHT_GRAY);
            g.fillRect(x, pHei - fontHeight, fontWidth, fontHeight);

            g.setFont(NOT_SAFE_MSG_FONT);
            g.setColor(Color.CYAN);
            g.drawString(notSafeMsg, x + padding, pHei - metrics.getDescent());
        }

        if (readOnly) {
            //四个角绘制图标
            for (int x = 0; x < 2; x++) {
                for (int y = 0; y < 2; y++) {
                    g.drawImage(ICON_READONLY,
                            x * (pWid - cellSize), y * (pHei - cellSize), cellSize, cellSize,
                            null);
                }
            }
        }
    }

    private void drawBackground(Graphics2D g) {
        if (backgroundImage == null) return;
        int pWid = getWidth();
        int pHei = getHeight();

        //模糊背景图:模糊图像未生成时绘制原背景
        BufferedImage blur = backgroundImageBlur != null ? backgroundImageBlur : backgroundImage;
        {
            //缩放模式是和[正常模式]相反的:图像会越过组件边界,但不会使背景有空白
            double rParent = pWid / (pHei + 0d);
            double rImage = blur.getWidth() / (blur.getHeight() + 0d);
            int bgWidth, bgHeight;
            //这里是相反的
            if (rParent < rImage) {
                bgHeight = pHei;
                bgWidth = (int) (bgHeight * rImage);
            } else {
                bgWidth = pWid;
                bgHeight = (int) (bgWidth / rImage);
            }
            int paddingX = (pWid - bgWidth) / 2;
            int paddingY = (pHei - bgHeight) / 2;
            g.drawImage(blur,
                    paddingX, paddingY, bgWidth, bgHeight,
                    null);
        }

        //背景图
        double rImage = bgWidth / (bgHeight + 0d);
        int bgWidth, bgHeight;

        if (mapItem.isLockBgScale()) {
            //锁定比例:参照物是地图元素的比例
            int col = mapItem.getWidth();
            int row = mapItem.getHeight();
            double rMap = col / (row + 0d);
            if (rMap < rImage) {
                //上下空白
                bgWidth = cellSize * col;
                bgHeight = (int) (bgWidth / rImage);
            } else {
                //左右空白
                bgHeight = cellSize * row;
                bgWidth = (int) (bgHeight * rImage);
            }
        } else {
            //不锁定比例:参照物是本组件
            double rParent = pWid / (pHei + 0d);
            if (rParent < rImage) {
                //上下空白
                bgWidth = pWid;
                bgHeight = (int) (bgWidth / rImage);
            } else {
                //左右空白
                bgHeight = pHei;
                bgWidth = (int) (bgHeight * rImage);
            }
        }

        int paddingX = (pWid - bgWidth) / 2;
        int paddingY = (pHei - bgHeight) / 2;
        g.drawImage(backgroundImage,
                paddingX, paddingY, bgWidth, bgHeight,
                null);
    }

    /**
     * 绘制单元格:图像,文字
     */
    private void drawCells(Graphics2D g) {
        int col = mapItem.getWidth();
        int row = mapItem.getHeight();

        //墙内,半透明白色
        g.setColor(EMPTY_COLOR);
        //关卡设计:保存时,如果是有效的地图,就不绘制单元格文字
        boolean safeMap = !mapItem.getAliArea().isEmpty() && mapItem.getNotSafeMsg() == null;
        for (Integer i : mapItem.getAliArea()) {
            int x = i % col;
            int y = i / col;
            g.fillRect(paddingLeft + x * cellSize, paddingTop + y * cellSize,
                    cellSize, cellSize);
        }

        if (isDesigner) g.setColor(Color.CYAN);
        for (int x = 0; x < col; x++) {
            for (int y = 0; y < row; y++) {
                int i = col * y + x;
                int intValue = mapItem.getMap()[i];
                Image image = ElementType.getImage(intValue);
                g.drawImage(
                        image,
                        paddingLeft + x * cellSize, paddingTop + y * cellSize,
                        cellSize, cellSize,
                        null);

                if (isDesigner && !safeMap) {
                    //单元格位置
                    String loc = x + "," + y;
                    g.drawString(loc, paddingLeft + x * cellSize, paddingTop + y * cellSize + cellSize - 5);//上移5px,解决'逗号'底部被下一行遮盖
                }

            }
        }

        if (isDesigner) {
            //地图大小,最后绘制是防止被元素遮挡
            String sizeStr = col + "x" + row;
            FontMetrics metrics = getFontMetrics(getFont());
            int numHeight = metrics.getAscent() - (metrics.getLeading() + metrics.getDescent());
            g.drawString(sizeStr, paddingLeft, paddingTop + numHeight);
        }
    }

    /**
     * 只画改变的区域,作用是优化性能
     *
     * @deprecated 弃用原因:绘制局部背景时,会产生图像断层(不可避免的int整除问题导致的);
     * 解决方法:组件改变大小时,重绘等尺寸的背景图,应该就可以解决
     */
    @Deprecated
    public void notifyDataUpdated(ArrayList<Integer> movePoints) {
        int pWid = getWidth();
        int pHei = getHeight();
        int col = mapItem.getWidth();
        int row = mapItem.getHeight();
        //每个元素格子的尺寸
        int iWid = pWid / col;
        int iHei = pHei / row;
        //不能整除的余数偏移量
        int dx = (pWid - iWid * col) / 2;
        int dy = (pHei - iHei * row) / 2;

        //背景的尺寸
        int bgIWid = bgWidth / col;
        int bgIHei = bgHeight / row;

        Graphics g = getGraphics();
        if (isDesigner) g.setColor(Color.CYAN);
        try {
            for (Integer i : movePoints) {
                int x = i % col;
                int y = i / col;
                //绘制背景
                g.drawImage(
                        backgroundImage,
                        //被画的位置
                        dx + x * iWid, dy + y * iHei,
                        dx + x * iWid + iWid, dy + y * iHei + iHei,
                        //要画的图像的局部位置:这里会产生图像断层!!因为整除问题
                        x * bgIWid, y * bgIHei,
                        x * bgIWid + bgIWid, y * bgIHei + bgIHei,
                        null);

                //墙内,半透明白色

                //绘制元素
                int intValue = mapItem.getMap()[i];
                Image image = ElementType.getImage(intValue);
                g.drawImage(
                        image,
                        dx + x * iWid, dy + y * iHei,
                        iWid, iHei,
                        null);

                if (isDesigner) {
                    if (x == 0 && y == 0) {
                        //地图大小
                        String size = col + "x" + row;
                        FontMetrics metrics = getFontMetrics(getFont());
                        int numHeight = metrics.getAscent() - (metrics.getLeading() + metrics.getDescent());
                        g.drawString(size, 0, numHeight);
                    }
                    //单元格位置
                    String loc = x + "," + y;
                    g.drawString(loc, dx + x * iWid, dy + y * iHei + iHei);
                }
            }
        } finally {
            g.dispose();
        }
    }

    /**
     * 如果是Jlist中的item,则要执行此方法
     */
    public void setParentList(JList<MapItem> jList, int index) {
        parentList = jList;
        curIndexInList = index;
    }

    public void setData(MapItem mapItem) {
        this.mapItem = mapItem;
        notifyDataUpdated();
    }

    public void notifyDataUpdated() {
        updateCellBounds();
        //更新背景图
        String imagePath = mapItem.getBackgroundImage();
        if (!Objects.equals(lastBackgroundPath, imagePath)) {
            ImageCacherObserver backgroundObserver = new ImageCacherObserver() {
                final JList requestList = parentList;
                final int requestListIndex = curIndexInList;

                @Override
                public String getKey() {
                    return imagePath == null ? MapConstant.INNER_BACKGROUND : imagePath;
                }

                @Override
                public ImageInfo createImage() {
                    String outerBackgroundErr = null;
                    Image loaded = null;
                    String userDefaultBackground = getUserDefaultBackground();
                    if (imagePath != null || userDefaultBackground != null) {
                        try {
                            if (imagePath != null) {
                                loaded = IconUtil.getFileImage(imagePath, "背景图");
                            } else {
                                loaded = IconUtil.getFileImage(userDefaultBackground, "背景图");
                            }
                        } catch (Exception e) {
                            ErrInfo errInfo = AppExceptionHandler.handleNoLog(e);
                            outerBackgroundErr = errInfo.getErrMsg();
                        }
                    }
                    if (loaded == null) {
                        //无自定义背景或自定义背景异常时加载内置背景
                        loaded = IconUtil.getIcon(MapConstant.INNER_BACKGROUND, "内置背景").getImage();
                    }
                    log.debug("imagePath={}", imagePath);
                    //ThreadUtil.sleep(ThreadLocalRandom.current().nextInt(300, 3000));
                    return new ImageInfo(ImageUtil.getImage(loaded), outerBackgroundErr);
                }

                /**
                 * @return nullable,获取 {@link MapConstant#MAP_DIR}下第一个以"background"开头的文件路径;
                 */
                private String getUserDefaultBackground() {
                    File mapDir = new File(MapConstant.MAP_DIR);
                    if (!mapDir.exists()) return null;
                    String[] userBg = mapDir.list((dir, name) -> name.startsWith(MapConstant.USER_DEFAULT_BG_PREFIX));
                    return userBg != null && userBg.length != 0 ? MapConstant.MAP_DIR + userBg[0] : null;
                }

                @Override
                public void onImageCreated(ImageInfo imageInfo, int width, int height) {
                    SwingUtilities.invokeLater(() -> {
                        updateBackgroundFields(imageInfo, this);
                        if (requestList == null) {
                            log.debug("刷新独立view");
                            repaint();
                        } else {
                            /*
                            在list中,仅刷新指定item
                            直接 requestList.repaint();会导致重绘n个item,重复调用了imageCacher.getImage
                                item背景会错乱,未深究哪里错误导致的
                            但本方法实际上还是让JList重绘了n个子item,会导致重复调用(swing的坑太深了╮(╯_╰)╭)
                                但至少item背景没有错乱
                             */
                            ((DefaultListModel2) requestList.getModel()).fireItemChanged(requestListIndex);
                        }
                    });
                }
            };
            ImageInfo imageInfo = imageCacher.getImage(backgroundObserver);
            if (imageInfo != null) {
                updateBackgroundFields(imageInfo, backgroundObserver);
            } else {
                backgroundImage = null;//要重置!
                outerBackgroundErr = null;
            }

            lastBackgroundPath = imagePath;
        }
        //重绘
        repaint();
    }

    private void updateBackgroundFields(ImageInfo imageInfo, ImageCacherObserver backgroundObserver) {
        if (imageInfo.image != null) {
            //log.info("requestList.isNull={}", parentList == null);
            backgroundImage = imageInfo.image;
            bgWidth = imageInfo.image.getWidth();
            bgHeight = imageInfo.image.getHeight();
            outerBackgroundErr = imageInfo.errMsg;//有图,不代表无错误
            ImageInfo blurInfo = imageCacher.getImage(new ImageCacherObserver() {
                final BufferedImage toBlur = backgroundImage;
                final JList requestList = parentList;
                final int requestListIndex = curIndexInList;

                @Override
                public String getKey() {
                    return backgroundObserver.getKey() + "-blur";
                }

                @Override
                public ImageInfo createImage() {
                    GaussianBlur gaussianBlur = new GaussianBlur();
                    BufferedImage blur = gaussianBlur.blur(toBlur);
                    gaussianBlur.destroy();
                    return new ImageInfo(blur, null);
                }

                @Override
                public void onImageCreated(ImageInfo imageInfo, int width, int height) {
                    backgroundImageBlur = imageInfo.image;
                    if (requestList == null) {
                        log.debug("刷新独立view");
                        repaint();
                    } else {
                            /*
                            在list中,仅刷新指定item
                            直接 requestList.repaint();会导致重绘n个item,重复调用了imageCacher.getImage
                                item背景会错乱,未深究哪里错误导致的
                            但本方法实际上还是让JList重绘了n个子item,会导致重复调用(swing的坑太深了╮(╯_╰)╭)
                                但至少item背景没有错乱
                             */
                        ((DefaultListModel2) requestList.getModel()).fireItemChanged(requestListIndex);
                    }
                }
            });
            if (blurInfo != null) {
                backgroundImageBlur = blurInfo.image;
            } else {
                backgroundImageBlur = null;
            }
        } else {
            backgroundImage = null;
            outerBackgroundErr = imageInfo.errMsg;
            backgroundImageBlur = null;
        }
    }

    /**
     * 根据鼠标坐标,获取对应的单元格在map中的index
     *
     * @return -1:在地图图像外
     */
    public int getCellIndex(MouseEvent e) {
        int eX = e.getX();
        int eY = e.getY();
        double x = (eX - paddingLeft + 0d) / cellSize;
        double y = (eY - paddingTop + 0d) / cellSize;
        log.debug("e=[{},{}], paddingLeft={}, paddingTop={}, x,y=[{},{}]", eX, eY, paddingLeft, paddingTop, x, y);
        int mWidth = mapItem.getWidth();
        int mHeight = mapItem.getHeight();
        if (x < 0 || x >= mWidth || y < 0 || y >= mHeight) {
            return -1;
        }
        return (int) y * mWidth + (int) x;
    }

}
